package net.neocat.tcx.file;

import com.google.common.io.Files;
import net.neocat.tcx.util.DomUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: jschek
 * Date: 4/24/13
 * Time: 11:28 PM
 * To change this template use File | Settings | File Templates.
 */
public class TcxWriter {

    private static final String NS_TCX = "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2";
    private static final ThreadLocal<DateFormat> DATE_FORMAT = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        }
    };

    public Document write(File tcxFile, TcxCourse course) throws IOException, TcxException {
        return writeStream(new PrintStream(Files.newOutputStreamSupplier(tcxFile).getOutput()), course);
    }

    public Document write(PrintStream out, TcxCourse course) throws IOException, TcxException {
        return writeStream(out, course);
    }

    public Document writeStream(PrintStream out, TcxCourse course) throws IOException, TcxException {
        try {
            DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
            docBuilderFactory.setNamespaceAware(true);
            DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
            Document doc = docBuilder.newDocument();
            doc.setXmlStandalone(true);

            writeDocument(doc, course);

            try {
                TransformerFactory transformerFactory = TransformerFactory.newInstance();
                Transformer transformer = transformerFactory.newTransformer();
                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
                DOMSource source = new DOMSource(doc);
                StreamResult result = new StreamResult(out);
                transformer.transform(source, result);
            } catch (TransformerException e) {
                throw new TcxException(e);
            }
            return doc;
        } catch (ParserConfigurationException e) {
            throw new IOException("Error parsing XML.", e);
        }
    }

    public Element writeDocument(Document document, TcxCourse course) throws IOException, TcxException {
        Element rootElement = document.createElement("TrainingCenterDatabase");
        document.appendChild(rootElement);

        rootElement.setAttribute("xmlns", "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2");
        rootElement.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
        rootElement.setAttribute("xsi:schemaLocation", "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd");

        DomUtils.appendElement(rootElement, "Folders");
        Element coursesElement = DomUtils.appendElement(rootElement, "Courses");
        Element courseElement = DomUtils.appendElement(coursesElement, "Course");
        DomUtils.appendElement(courseElement, "Name", course.getCourseName());

        Element lapElement = DomUtils.appendElement(courseElement, "Lap");
        DomUtils.appendElement(lapElement, "TotalTimeSeconds", course.getTotalTimeSeconds());
        DomUtils.appendElement(lapElement, "DistanceMeters",
                String.format("%.1f", course.getTotalDistanceMeters()));
        appendPosition(lapElement, "BeginPosition", course.getBeginPosition());
        appendPosition(lapElement, "EndPosition", course.getEndPosition());
        DomUtils.appendElement(lapElement, "Intensity", "Active");

        appendTrackPoints(courseElement,course.getTrackPoints());
        appendCoursePoints(courseElement,course.getCoursePoints());
        return rootElement;
    }

    private void appendCoursePoints(Element courseElement, List<CoursePoint> coursePoints) {
        for(CoursePoint coursePoint : coursePoints) {
            Element element = DomUtils.appendElement(courseElement,"CoursePoint");
            DomUtils.appendElement(element, "Name", coursePoint.getName());
            DomUtils.appendElement(element, "Time", DATE_FORMAT.get().format(new Date(coursePoint.getTimestamp())));
            appendPosition(element, "Position", coursePoint.getPosition());
            DomUtils.appendElement(element, "PointType", coursePoint.getPointType());
            DomUtils.appendElement(element, "Notes", coursePoint.getNotes());
        }
    }

    private Element appendTrackPoints(Element courseElement, List<TrackPoint> trackPoints) {
        Element trackElement = DomUtils.appendElement(courseElement, "Track");
        for(TrackPoint trackPoint : trackPoints) {
            Element element = DomUtils.appendElement(trackElement, "Trackpoint");
            DomUtils.appendElement(element, "Time", DATE_FORMAT.get().format(new Date(trackPoint.getTimestamp())));
            appendPosition(element, "Position", trackPoint.getPosition());
            DomUtils.appendElement(element, "AltitudeMeters",
                    String.format("%.1f", trackPoint.getAltitudeMeters()));
            DomUtils.appendElement(element, "DistanceMeters",
                String.format("%.1f", trackPoint.getDistanceMeters()));
        }
        return null;
    }

    private Element appendPosition(Element parent, String name, GeoPosition position) {
        Element positionElement = DomUtils.appendElement(parent, name);
        DomUtils.appendElement(positionElement, "LatitudeDegrees",
                String.format("%.5f",position.getLatitude()));
        DomUtils.appendElement(positionElement, "LongitudeDegrees",
                String.format("%.5f", position.getLongitude()));
        return positionElement;
    }


}
